package org.zebra.web.config;

import cn.hutool.core.collection.CollUtil;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.hibernate.validator.HibernateValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.PathMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.zebra.common.core.GeneralYmlPropertySourceFactory;
import org.zebra.common.crypto.AesCryptoProvider;
import org.zebra.common.crypto.CryptoProvider;
import org.zebra.common.crypto.RsaCryptoProvider;
import org.zebra.common.crypto.Sm2CryptoProvider;
import org.zebra.common.util.UriWhiteUtil;
import org.zebra.web.advice.*;
import org.zebra.web.advice.crypto.ApiDecryptRequestBodyAdvice;
import org.zebra.web.advice.crypto.ApiEncryptResponseBodyAdviceChain;
import org.zebra.web.annotation.crypto.AlgorithmType;
import org.zebra.web.annotation.sign.CheckSignAspect;
import org.zebra.web.annotation.sign.CheckSignStrategy;
import org.zebra.web.annotation.sign.impl.DefaultCheckSignStrategy;
import org.zebra.web.annotation.version.ApiVersionRequestMappingHandlerMapping;
import org.zebra.web.converter.EnumConverterFactory;
import org.zebra.web.converter.StringToDateConverter;
import org.zebra.web.filter.RepeatableFilter;
import org.zebra.web.filter.XssFilter;
import org.zebra.web.jackson.BigNumberSerializer;
import org.zebra.web.sse.SseEmitterController;
import org.zebra.web.sse.SseEmitterService;
import org.zebra.web.support.ApiCryptoCondition;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * 核心配置
 *
 * @author zhanghongbin
 */
@Configuration
@EnableConfigurationProperties({ZebraProperties.class})
@PropertySource(value = "classpath:web-springboot.yml", factory = GeneralYmlPropertySourceFactory.class)
public class WebConfig implements WebMvcRegistrations, WebMvcConfigurer {

    @Bean
    @ConditionalOnMissingBean(value = GlobalExceptionAdvice.class)
    public GlobalExceptionAdvice globalExceptionAdvice() {
        return new GlobalExceptionAdvice();
    }

    @Bean
    @ConditionalOnMissingBean(NotVoidResponseBodyAdviceChain.class)
    public NotVoidResponseBodyAdviceChain notVoidResponseBodyAdvice(
            @Autowired ZebraProperties zebraProperties,
            @Autowired Environment environment,
            @Autowired PathMatcher pathMatcher,
            @Autowired(required = false) CryptoProvider encryptor) {
        Set<String> excludeUrls = new HashSet<>();
        ZebraProperties.GlobalAdvice globalAdvice = zebraProperties.getGlobalAdvice();
        if (globalAdvice.getExcludes().length != 0) {
            excludeUrls.addAll(Arrays.asList(globalAdvice.getExcludes()));
        }
        NotVoidResponseBodyAdviceChain notVoidResponseBodyAdvice =
                new NotVoidResponseBodyAdviceChain(excludeUrls, pathMatcher);
        ApiEncryptResponseBodyAdviceChain apiDecryptResponseBodyAdvice = null;
        if (encryptor != null) {
            apiDecryptResponseBodyAdvice = new ApiEncryptResponseBodyAdviceChain(encryptor);
            notVoidResponseBodyAdvice.setBaseResponseBodyAdvice(apiDecryptResponseBodyAdvice);
        }
        if (Boolean.parseBoolean(environment.getProperty("zebra.log.http.enabled", "false"))) {
            LogNotVoidResponseBodyAdviceChain logNotVoidResponseBodyAdvice = new LogNotVoidResponseBodyAdviceChain();
            if (apiDecryptResponseBodyAdvice != null) {
                apiDecryptResponseBodyAdvice.setBaseResponseBodyAdvice(logNotVoidResponseBodyAdvice);
            } else {
                notVoidResponseBodyAdvice.setBaseResponseBodyAdvice(logNotVoidResponseBodyAdvice);
            }
        }
        return notVoidResponseBodyAdvice;
    }

    @Bean
    @ConditionalOnMissingBean(VoidResponseBodyAdvice.class)
    public VoidResponseBodyAdvice voidResponseBodyAdvice() {
        return new VoidResponseBodyAdvice();
    }

    /**
     * 根据ApiDecryptCondition 条件判断是否注入Encryptor对象
     * 主要目的是为了避免请求和响应性能损失
     *
     * @param zebraProperties 配置文件
     * @return 加密提供者对象
     */
    @Bean
    @Order(Integer.MIN_VALUE)
    @Conditional(ApiCryptoCondition.class)
    public CryptoProvider encryptor(@Autowired ZebraProperties zebraProperties) {
        String algorithm = zebraProperties.getCrypto().getAlgorithm();
        AlgorithmType algorithmType = Enum.valueOf(AlgorithmType.class, algorithm.toUpperCase());
        if (algorithmType == AlgorithmType.AES) {
            return new AesCryptoProvider(zebraProperties.getCrypto().getSecretKey());
        } else if (algorithmType == AlgorithmType.SM2) {
            return new Sm2CryptoProvider(
                    zebraProperties.getCrypto().getPublicKey(),
                    zebraProperties.getCrypto().getPrivateKey());
        } else {
            return new RsaCryptoProvider(
                    zebraProperties.getCrypto().getPublicKey(),
                    zebraProperties.getCrypto().getPrivateKey());
        }
    }

    @Bean
    @ConditionalOnBean(CryptoProvider.class)
    public ApiDecryptRequestBodyAdvice apiDecryptRequestBodyAdvice(
            @Autowired(required = false) CryptoProvider encryptor) {
        return new ApiDecryptRequestBodyAdvice(encryptor);
    }

    @Bean
    @ConditionalOnMissingBean(CheckSignAspect.class)
    public CheckSignAspect checkSignAspect(
            @Autowired(required = false) CheckSignStrategy checkSignStrategy,
            @Autowired ZebraProperties zebraProperties) {
        if (checkSignStrategy == null) {
            return new CheckSignAspect(
                    new DefaultCheckSignStrategy(zebraProperties.getSign().getSecretKey()));
        }
        return new CheckSignAspect(checkSignStrategy);
    }

    /**
     * long类型转string
     *
     * @return 对象
     */
    @Bean
    @ConditionalOnMissingBean
    public Jackson2ObjectMapperBuilderCustomizer jacksonBuilder() {
        return jacksonObjectMapperBuilder -> {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            jacksonObjectMapperBuilder
                    .serializerByType(Long.class, BigNumberSerializer.INSTANCE)
                    .serializerByType(Long.TYPE, BigNumberSerializer.INSTANCE)
                    .serializerByType(BigInteger.class, BigNumberSerializer.INSTANCE)
                    .serializerByType(BigDecimal.class, BigNumberSerializer.INSTANCE)
                    .serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(formatter))
                    .deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(formatter))
                    .timeZone(TimeZone.getDefault());
        };
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverterFactory(new EnumConverterFactory());
        registry.addConverter(new StringToDateConverter());
    }

    /**
     * 配置跨域
     * 默认为启用
     *
     * @return 对象
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "zebra.web.cors", name = "enabled", havingValue = "true", matchIfMissing = true)
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        // 设置访问源地址
        config.addAllowedOriginPattern("*");
        // 设置访问源请求头
        config.addAllowedHeader("*");
        // 设置访问源请求方法
        config.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new ApiVersionRequestMappingHandlerMapping();
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        int index = 0;
        for (int i = 0; i < converters.size(); i++) {
            if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {
                index = i;
                break;
            }
        }
        HttpMessageConverter httpMessageConverter = converters.remove(index);
        converters.add(0, httpMessageConverter);
    }

    @Bean
    public Validator validator() {
        // true-遇到一个错误立即返回不在往下校验，
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .addProperty("hibernate.validator.fail_fast", "true")
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();
        return validator;
    }

    /**
     * 可重复读流配置
     *
     * @param pathMatcher 路径匹配器
     * @return 对象
     */
    @Bean
    public FilterRegistrationBean<RepeatableFilter> requestBodyCacheFilter(@Autowired PathMatcher pathMatcher) {
        FilterRegistrationBean bean = new FilterRegistrationBean<>(new RepeatableFilter(pathMatcher));
        bean.setOrder(-100000);
        return bean;
    }

    /**
     * xss
     *
     * @param zebraProperties 配置文件对象
     * @param pathMatcher     路径匹配器
     * @return 对象
     */
    @Bean
    @ConditionalOnProperty(value = "zebra.web.xss.enabled", havingValue = "true", matchIfMissing = true)
    public FilterRegistrationBean xssFilterRegistration(
            @Autowired ZebraProperties zebraProperties, @Autowired PathMatcher pathMatcher) {
        ZebraProperties.Xss xss = zebraProperties.getXss();
        FilterRegistrationBean registration = new FilterRegistrationBean();
        Set<String> whiteSet = UriWhiteUtil.getDefaultUriWhite();
        whiteSet.addAll(CollUtil.toList(xss.getExcludes()));
        registration.setFilter(new XssFilter(whiteSet, pathMatcher));
        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
        return registration;
    }

    //    /**
    //     * web 服务配置
    //     * @return 对象
    //     */
    //    @Bean
    //    public AbstractServletWebServerFactory webServerFactory() {
    //        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    //        factory.addConnectorCustomizers((Connector connector) -> {
    //            connector.setProperty("relaxedPathChars", "\"<>[\\]^`{|}");
    //            connector.setProperty("relaxedQueryChars", "\"<>[\\]^`{|}");
    //        });
    //        return factory;
    //    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        // resolvers.add(new UnderlineToHumpArgumentProcessor(true));
    }

    @Bean
    @ConditionalOnProperty(prefix = "zebra.web.sse", name = "enabled", havingValue = "true")
    public SseEmitterService sseEmitterService() {
        return new SseEmitterService();
    }

    @Bean
    @DependsOn("sseEmitterService")
    @ConditionalOnProperty(prefix = "zebra.web.sse", name = "enabled", havingValue = "true")
    public SseEmitterController sseEmitterController() {
        return new SseEmitterController();
    }
}
